# 正则化 L2范数，Rdige归
# 调用sklearn.linear_model 的 Ridge函数
# 采用GridSearchCV 求出最好的 lambda


import numpy as np
import matplotlib.pyplot as plt
import sklearn.model_selection as ms
from sklearn.linear_model import Ridge
from sklearn.model_selection import GridSearchCV

plt.rcParams['font.sans-serif']=['SimHei'] #用来正常显示中文标签
plt.rcParams['axes.unicode_minus']=False #用来正常显示负

ratio = 0.3 #训练集与测试集分割比例
nums = 121 #数据点个数

x_data = np.linspace(-1, 1, nums).reshape(-1, 1)
y_data = x_data ** 2 # 真实数据是 平方

seed = 2021
np.random.seed(seed)   # 固定随机种子
y_data += np.random.randn(*x_data.shape) * 0.3  # 加上噪声

print('数据大小', y_data.shape, x_data.shape)

def constructData(x_data):  ## 构造数据，对应最高项 7次多项式
    x1 = x_data
    x2 = x_data ** 2
    x3 = x_data ** 3
    x4 = x_data ** 4
    x5 = x_data ** 5
    x6 = x_data ** 6
    x7 = x_data ** 7
    xx = np.hstack((x1, x2, x3, x4, x5, x6, x7))
    return xx

x_data = constructData(x_data)
x_train, x_test, y_train, y_test = ms.train_test_split(x_data, y_data, test_size=ratio, random_state=seed)

# rang = np.hstack((np.linspace(0, 1.5, 6), np.linspace(2, 10, 8), np.linspace(12, 20, 4))) ##构造lambda的枚举范围
rang = np.linspace(0, 15, 101) ##构造lambda的枚举范围

parameters = {'alpha': rang}
gs = GridSearchCV(Ridge(), param_grid=parameters, cv=10, verbose=1)
gs.fit(x_train, y_train)

print('最佳参数：', gs.best_params_)
min_lambda = gs.best_params_['alpha']
# plt.plot(rang, hist, '-')  # 画出所有lambda对应的均方误差
# plt.title('所有lambda对应均方误差')
# plt.xlabel('$\lambda$')
# plt.ylabel('RMSE')

# kk = np.argmin(hist)   # 找出最小的lambda的位置与数值
# min_lambda = rang[kk]
# print('最小均方误差为：', min_lambda, kk)   ## 输出最小均方误差的值与位置


###################################################
###用最优的lambda甲酸模型

model = Ridge(alpha=min_lambda)
model.fit(x_train, y_train)
print('最优模型参数：', model.coef_, model.intercept_)

y_pred = model.predict(x_test)
loss = np.mean(np.square(y_pred - y_test))   ## 均方误差
print('最优模型的均方误差：', loss)

# 绘图
plt.figure()

xx = np.linspace(-1, 1, nums * 10).reshape(-1, 1)
yy = xx ** 2  # 对应真实的实际值，用于绘图数据
xx2 = constructData(xx)
yy2 = model.predict(xx2)  #

plt.scatter(x_train[:, 0], y_train, label='训练集')
plt.scatter(x_test[:, 0], y_test, c='r', label='测试集')

plt.plot(xx, yy2, c='r', label='predict')
plt.plot(xx, yy, c='k', label='origin')
plt.legend(loc='lower right')
plt.title('测试集结果 $\lambda$ = %.3f, Cost = %.6f' % (min_lambda, loss), fontproperties='SimHei') # 需要把最后一个字体参数去掉，否则可能报错

plt.legend()
plt.show()